﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MinesweeperUtility
{
    public class Minesweeper : IMinesweeper, IDraw
    {
        private Cell[,] playground;
        private Difficulty difficulty;
        private int mines;
        private int minesLeft;
        private int leftPossibleCells;
        private Timer time;
        private string playerName;
        private string playerCurrentState;
        private int markedCells;
        private RankList top10 = new RankList(); //rank

        public Minesweeper(Level level, string playerName)
        {
            this.top10.LoadRankingList(); //rank
            this.playerName = playerName;
            this.time = new Timer();
            this.difficulty = new Difficulty(level);
            this.mines = this.difficulty.NumberOfBombs;
            this.minesLeft = this.mines;
            this.playground = new Cell[this.difficulty.Rows, this.difficulty.Cols];
            for (int i = 0; i < this.Rows; i++)
            {
                for (int j = 0; j < this.Cols; j++)
                {
                    this.playground[i, j] = new Cell();
                }
            }
            this.leftPossibleCells = this.playground.Length - this.mines;
        }

        public int LeftPossibleCells
        {
            get { return this.leftPossibleCells; }
            set { this.leftPossibleCells = value; }
        }

        public int Rows
        {
            get { return this.Playground.GetLength(0); }
        }

        public int Cols
        {
            get { return this.Playground.GetLength(1); }
        }

        public int Mines
        {
            get { return this.mines; }
            set { this.mines = value; }
        }

        public Cell[,] Playground
        {
            get { return this.playground; }
            set { this.playground = value; }
        }

        public void GenerateMinesweeper()
        {
            GenerateMines();
            FillCells();
        }

        public void GenerateMines()
        {
            int countMines = 0;
            int generatedRow = 0;
            int generatedCol = 0;
            Random rand = new Random();
            while (countMines < this.Mines)
            {
                generatedRow = rand.Next(0, this.Playground.GetLength(0));
                generatedCol = rand.Next(0, this.Playground.GetLength(1));
                if (this.Playground[generatedRow, generatedCol].Value >= 0)
                {
                    this.Playground[generatedRow, generatedCol].Value = -1;//Initialize a mine
                    countMines++;
                }
            }
        }

        public void FillCells()
        {
            for (int i = 0; i < this.Rows; i++)
            {
                for (int j = 0; j < this.Cols; j++)
                {
                    if (!this.Playground[i, j].IsMine())
                    {
                        this.Playground[i, j].Value = CountNeighbouringMines(i, j);
                    }
                }
            }
        }

        private int CountNeighbouringMines(int row, int col)
        {
            List<Direction> directions = new List<Direction>() 
            {
                new Direction(0,-1),//left
                new Direction(0,1),//right
                new Direction(-1,0),//up
                new Direction(1,0),//down
                new Direction(-1,-1),//up left
                new Direction(-1,1),//up right
                new Direction(1,-1),//down left
                new Direction(1,1),//down right
            };
            int countMines = 0;
            int x, y;
            foreach (Direction direction in directions)
            {
                x = row + direction.X;
                y = col + direction.Y;
                if (x >= 0 && x < this.Rows && y >= 0 && y < this.Cols && this.Playground[x, y].IsMine())
                {
                    countMines++;
                }
            }
            return countMines;
        }

        private void DrawFirstLine(int positionX, int positionY)
        {
            Console.SetCursorPosition(positionX, positionY);
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Time: {0}, Mines Left: {1} \n", this.time.ToString(), this.minesLeft.ToString());
        }

        public override string ToString()
        {
            StringBuilder minesweeper = new StringBuilder();

            string current = null;

            minesweeper.Append(string.Format("     Time: {0}", this.time.ToString()));    //
            minesweeper.Append("\n \n");     //

            minesweeper.Append("   ");
            for (int col = 0; col < this.Cols; col++)
            {
                current = string.Format("{0,3}", col);
                minesweeper.Append(current);
            }
            minesweeper.Append("\n  ");
            for (int col = 0; col < this.Cols; col++)
            {
                current = string.Format("{0,3}", "--");
                minesweeper.Append(current);
            }
            minesweeper.Append("\n");
            for (int row = 0; row < this.Rows; row++)
            {
                current = string.Format("{0,2}|", row);
                minesweeper.Append(current);
                for (int col = 0; col < this.Cols; col++)
                {
                    minesweeper.Append(string.Format("{0,3}", this.Playground[row, col].Value));
                }
                minesweeper.Append("\n\n");
            }
            return minesweeper.ToString();
        }

        public void Run()
        {
            string action, rowInput, colInput;
            int currentRow, currentCol;
            bool hasMine = false;

            Draw(-1, -1);               
            this.time.StartTimer();     
            try
            {
                while (!hasMine && this.LeftPossibleCells != 0)
                {
                    do
                    {
                        this.DrawFirstLine(0, 0);
                        Console.SetCursorPosition(0, Console.BufferHeight - 5); 
                        Console.Write("                                  \n     \n                                     \n    "); 
                        Console.SetCursorPosition(0, Console.BufferHeight - 5); 
                        Console.WriteLine("Type [f] to flag/unflag");
                        while (!Console.KeyAvailable)
                        {
                            this.DrawFirstLine(0, 0);
                            Console.SetCursorPosition(0, Console.BufferHeight - 4);
                            System.Threading.Thread.Sleep(100);
                        }
                        action = Console.ReadLine().ToLower();

                        this.DrawFirstLine(0, 0);   
                        Console.SetCursorPosition(0, Console.BufferHeight - 5); 
                        Console.Write("                                  \n     \n                                     \n    "); 
                        Console.SetCursorPosition(0, Console.BufferHeight - 5); 

                        Console.Write("Enter valid row [{0}...{1}]:", 0, this.Rows - 1); 

                        //Error in input fixed - Empty line for While cycle = Timer tick
                        while (!Console.KeyAvailable)
                        {
                            this.DrawFirstLine(0, 0);
                            Console.SetCursorPosition(0, Console.BufferHeight - 4);
                            System.Threading.Thread.Sleep(100); //Faster Threading
                        }
                        do
                        {
                            rowInput = Console.ReadLine();
                        } while (!int.TryParse(rowInput, out currentRow) || !(currentRow >= 0 && currentRow < this.Rows));

                        SelectRow(currentRow);
                        Console.Clear();
                        Draw(currentRow, 1);

                        this.DrawFirstLine(0, 0);                                                   
                        Console.SetCursorPosition(0, Console.BufferHeight - 3);                             
                        Console.Write("Enter valid col [{0}...{1}]:", 0, this.Cols - 1);    

                        while (!Console.KeyAvailable)
                        {
                            this.DrawFirstLine(0, 0);
                            Console.SetCursorPosition(0, Console.BufferHeight - 2);
                            System.Threading.Thread.Sleep(100);
                        }
                        colInput = Console.ReadLine();

                        DeselectRow(currentRow);
                        Console.Clear(); 
                        Draw(currentRow, 1);


                    } while (!int.TryParse(rowInput, out currentRow) || !int.TryParse(colInput, out currentCol)
                                || !(currentRow >= 0 && currentRow < this.Rows) || !(currentCol >= 0 && currentCol < this.Cols));

                    if (action == "f")
                    {
                        MarkCell(currentRow, currentCol);
                    }
                    else
                    {
                        hasMine = CheckCell(currentRow, currentCol);
                    }

                    Console.Clear(); 
                    Draw(currentRow, currentCol);
                }

                if (hasMine)
                {
                    throw new BoomException("BOOM");
                }
                else
                {
                    throw new WinException("WIN");
                }
            }
            catch (BoomException)
            {
                if (this.difficulty.Level == Level.Beginner)
                {
                    Console.WindowWidth += 20;
                    Console.WindowHeight += 10;
                }

                if (this.difficulty.Level == Level.Intermediate)
                {
                    Console.BufferHeight += 10;
                }

                if (this.difficulty.Level == Level.Expert)
                {
                    Console.BufferHeight += 10;
                }

                BoomEventArgs e = new BoomEventArgs("                                \n  Boooooom!" + this.top10.ToString(), ConsoleColor.Red);
                this.OnBoom(e);

                this.playerCurrentState = "have Lost!";

                this.time.StopTimer();                                  
                Console.WriteLine("                                ");  
                Console.Write("  GAME OVER!  " + new string(' ', Console.BufferWidth / 3 + 2) + " GAME OVER!     ");  
                Console.ForegroundColor = ConsoleColor.Gray;            
            }
            catch (WinException)
            {
                this.time.StopTimer();                                  
                this.top10.AddRanking(new Player((long)this.time.GetTime(), this.playerName, this.difficulty.Level)); // try to add to rankList
                this.minesLeft = 0;

                if (this.difficulty.Level == Level.Beginner)
                {
                    Console.WindowWidth += 20;
                    Console.WindowHeight += 10;
                }
                if (this.difficulty.Level == Level.Intermediate)
                {
                    Console.BufferHeight += 10;
                }

                if (this.difficulty.Level == Level.Expert)
                {
                    Console.BufferHeight += 10;
                }

                this.playerCurrentState = "have Won!";
                WinEventArgs e = new WinEventArgs("                                \n  Winner!" + this.top10.ToString(), ConsoleColor.Green);
                this.OnWin(e);

                Console.WriteLine("                                ");  
                Console.WriteLine("");
                Console.ForegroundColor = ConsoleColor.Gray;            
            }
            finally
            {
                ScoreBoard finalScoreBoard = new MinesweeperScoreBoard(this.playerName, this.playerCurrentState, this.time.ToString(), this.minesLeft, this.difficulty.Level.ToString());
                finalScoreBoard.Draw(Console.BufferWidth / 4, Console.BufferHeight - 1);
                Console.BufferHeight += 1;
                Console.WindowHeight += 1;
                Console.SetCursorPosition(0, Console.BufferHeight - 1);
            }
        }

        private bool CheckCell(int currentRow, int currentCol)
        {
            Cell currentCell = Playground[currentRow, currentCol];
            if (!currentCell.IsVisited && !currentCell.IsMarked)
            {
                this.LeftPossibleCells--;
                currentCell.IsVisited = true;
                if (currentCell.IsMine())
                {
                    RevealAllMines();
                    return true;
                }

                if (currentCell.Value == 0)
                {
                    RevealNeighbouring(currentRow, currentCol);
                }
            }
            return false;
        }

        private void RevealNeighbouring(int currentRow, int currentCol)
        {
            Cell currentCell = Playground[currentRow, currentCol];
            if (!currentCell.IsMarked)
            {
                List<Direction> directions = new List<Direction>() 
            {
                new Direction(0,-1),//left
                new Direction(0,1),//right
                new Direction(-1,0),//up
                new Direction(1,0),//down
                new Direction(-1,-1),//up left
                new Direction(-1,1),//up right
                new Direction(1,-1),//down left
                new Direction(1,1),//down right
            };
                int x, y;
                foreach (Direction direction in directions)
                {
                    x = currentRow + direction.X;
                    y = currentCol + direction.Y;
                    if (x >= 0 && x < this.Rows && y >= 0 && y < this.Cols && !this.Playground[x, y].IsVisited && !this.Playground[x, y].IsMarked)
                    {
                        this.Playground[x, y].IsVisited = true;
                        this.LeftPossibleCells--;
                        if (this.Playground[x, y].Value == 0)
                        {
                            RevealNeighbouring(x, y);
                        }
                    }
                }
            }
        }

        private void RevealAllMines()
        {
            for (int i = 0; i < this.Rows; i++)
            {
                for (int j = 0; j < this.Cols; j++)
                {
                    if (this.Playground[i, j].IsMine())
                    {
                        this.Playground[i, j].IsVisited = true;
                    }
                }
            }
        }

        private void MarkCell(int currentRow, int currentCol)
        {
            Cell currentCell = Playground[currentRow, currentCol];
            if (!currentCell.IsMarked && !currentCell.IsVisited)
            {
                this.minesLeft--;
                this.markedCells++;
                currentCell.IsMarked = true;
            }
            else if (currentCell.IsVisited)
            {
            }
            else
            {
                this.minesLeft++;
                this.markedCells++;
                currentCell.IsMarked = false;
            }
        }

        private void SelectRow(int currentRow)
        {
            int length = int.Parse(this.Cols.ToString());
            for (int i = 0; i < length; i++)
            {
                Playground[currentRow, i].IsSelected = true;
            }
        }

        private void DeselectRow(int currentRow)
        {
            int length = int.Parse(this.Cols.ToString());
            for (int i = 0; i < length; i++)
            {
                Playground[currentRow, i].IsSelected = false;
            }
        }

        public void Draw(int currentRow, int currentCol)
        {
            this.DrawFirstLine(0, 0);
            this.difficulty.Draw(0, 1);
            Console.Write("   ");
            string current = null;

            for (int col = 0; col < this.Cols; col++)
            {
                current = string.Format("{0,3}", col);
                Console.Write(current);
            }
            Console.Write("\n  ");
            for (int col = 0; col < this.Cols; col++)
            {
                current = string.Format("{0,3}", "--");
                Console.Write(current);
            }
            Console.Write("\n");
            for (int row = 0; row < this.Rows; row++)
            {
                current = string.Format("{0,2}|", row);
                Console.Write(current);
                for (int col = 0; col < this.Cols; col++)
                {
                    if (row == currentRow && col == currentCol)
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                    }

                    if (this.Playground[row, col].IsMarked && this.Playground[row, col].IsSelected)
                    {
                        Console.ForegroundColor = ConsoleColor.Yellow;
                        Console.Write("  F");
                    }
                    else if (this.Playground[row, col].IsMarked)
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write("  F");
                    }

                    else if (this.Playground[row, col].IsSelected && !this.Playground[row, col].IsVisited)
                    {
                        Console.ForegroundColor = ConsoleColor.Yellow;
                        Console.Write("  *");
                    }
                    else if (!this.Playground[row, col].IsVisited && !this.Playground[row, col].IsMarked)
                    {
                        Console.ForegroundColor = ConsoleColor.Gray;
                        Console.Write("  *"); // 
                    }
                    else
                    {
                        if (this.Playground[row, col].IsMine())
                        {
                            Console.ForegroundColor = ConsoleColor.Red;
                        }
                        Console.Write(string.Format("{0,3}", this.Playground[row, col].Value));
                    }
                    Console.ForegroundColor = ConsoleColor.Green; //
                }
                Console.Write("\n\n");
            }
            Console.ForegroundColor = ConsoleColor.Gray;
        }

        public event BoomEventHandler Boom;

        public void OnBoom(BoomEventArgs e)
        {
            if (this.Boom != null)
            {
                this.Boom(e);
            }
        }

        public event WinEventHandler Win;

        public void OnWin(WinEventArgs e)
        {
            if (this.Win != null)
            {
                this.Win(e);
            }
        }
    }
}